0%

关于Java 8中-XX:MetaspaceSize的一点误解

背景

最近手头有个新项目做压测,本来打算一边压测一边寻找瓶颈调整JVM options。在分析压测时的服务的GC日志过程中,发现有很多类似这样的Full GC记录:

1
[Full GC (Metadata GC Threshold) ...]

整个应用的GC日志看起来挺正常的,就是这类Full GC有点多,不过在压测一段时间后,就不再出现了。

分析&处理

首先需要明确一点,从Java 8开始,永久代(Perm Generation)被移到了元空间(Metaspace),Metaspace默认没有上限,与物理内存一致。

上面提到的Full GC主要是元空间的resize过程引起的。类似于堆空间会随着应用负载情况动态调整(从Xms 慢慢增长到Xmx),元空间也有类似的机制。但是元空间的resize是在Full GC下操作的。所以这就解释了上面的现象,应用启动时,元空间内存较小,随着应用负载增大,它需要多次resize,而每次resize,就是一次Full GC。

对于堆内存,解决resize带来的问题,常见的做法是固定一个较大的Xms,或者直接Xms=Xmx。想当然地,我也以为元空间是类似的做法,翻一翻元空间相关的JVM参数,找到一个 -XX:MetaspaceSize,设置成之前压测负载时元空间的大小。重新做一次压测,GC日志里不再有[Full GC (Metadata GC Threshold) ...] 字眼,问题解决。

但是本着钻研精神,压测时我多看了一眼元空间的内存使用情况,增长曲线仍然跟设置参数之前一样,也就是元空间的“初始大小”根本没有因为“-XX:MetaspaceSize”被设置。但是问题还是解决了,为什么呢?

后来仔细查阅了一下Oracle文档Metaspace那一节,发现我对这个“-XX:MetaspaceSize“ 参数有本质误解:它不是设置元空间的初始大小,而是设置触发元空间Full GC的阈值(就是原文中的‘high-water mark’)。

When the space committed for class metadata reaches a certain level (a high-water mark), a garbage collection is induced. After the garbage collection, the high-water mark may be raised or lowered depending on the amount of space freed from class metadata. The high-water mark would be raised so as not to induce another garbage collection too soon. The high-water mark is initially set to the value of the command-line option -XX:MetaspaceSize.

也就是说,-XX:MetaspaceSize 设置得大一点,由于metaspace 引起的Full GC时机就晚一点,如果XX:MetaspaceSize比应用实际运行时的metaspace高位还大,那基本就不会触发metaspace Full GC了。

仔细搜下,SO上也有人有类似的问题,看来XX:MetaspaceSize这个变量名真的很让人confused:https://stackoverflow.com/questions/39491325/understanding-metaspace-size